﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Xml;
using QQ2564874169.Core;
using QQ2564874169.Core.Caching;
using QQ2564874169.Core.Encryption;
using QQ2564874169.Core.Utils;
using QQ2564874169.Tencent.Weixin.Messages;
using QQ2564874169.Tencent.Weixin.Models;
using QQ2564874169.Tencent.Weixin.Response;

namespace QQ2564874169.Tencent.Weixin
{
    public class PublicPlatform
    {
        public static Func<ICache> GetCache { get; set; }

        private static ICache _cache = new AppCache();
        private string _appid;
        private string _appsecret;
        private string _accessToken;
        private string _jsApiTicket;
        private DateTime _accessToken_expires;
        private DateTime _jsApiTicket_expires;

        static PublicPlatform()
        {
            GetCache = () => _cache;
        }

        public PublicPlatform(string appid, string appsecret)
        {
            _appid = appid;
            _appsecret = appsecret;
        }

        public string GetAccessToken()
        {
            if (_accessToken == null || _accessToken_expires < DateTime.Now)
            {
                lock (GetCache)
                {
                    RefreshAccessToken();
                }
            }
            return _accessToken;
        }

        public string GetJsApiTicket()
        {
            if (_jsApiTicket == null || _jsApiTicket_expires < DateTime.Now)
            {
                lock (GetCache)
                {
                    RefreshJsApiTicket();
                }
            }
            return _jsApiTicket;
        }

        private void RefreshAccessToken()
        {
            var cache = GetCache();
            var key = "RefreshAccessToken_" + _appid;
            var token = cache.Get<WXAccessToken>(key);
            if (token == null)
            {
                token = LoadAccessToken();
                cache.Save(new CacheItem
                {
                    Key = key,
                    Value = token,
                    AbsoluteExpiration = token.expires_in
                });
            }
            _accessToken = token.access_token;
            _accessToken_expires = token.create_time.ToDate().AddSeconds(token.expires_in);
        }

        private void RefreshJsApiTicket()
        {
            var cache = GetCache();
            var key = "RefreshJsApiTicket_" + GetAccessToken();
            var ticket = cache.Get<WXJsTicket>(key);
            if (ticket == null)
            {
                ticket = LoadJsApiTicket();
                cache.Save(new CacheItem
                {
                    Key = key,
                    Value = ticket,
                    AbsoluteExpiration = ticket.expires_in
                });
            }
            _jsApiTicket = ticket.ticket;
            _jsApiTicket_expires = ticket.create_time.ToDate().AddSeconds(ticket.expires_in);
        }

        public WXSnsToken GetSnsToken(string code)
        {
            var url = $"https://api.weixin.qq.com/sns/oauth2/access_token?appid={_appid}&secret={_appsecret}&code={code}&grant_type=authorization_code";
            var res = HttpHelper.Get(url).Read();
            return res.JsonTo<WXSnsToken>();
        }

        protected virtual WXAccessToken LoadAccessToken()
        {
            return HttpHelper.Get(
                    $"https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid={_appid}&secret={_appsecret}")
                .Read().JsonTo<WXAccessToken>();
        }

        protected virtual WXJsTicket LoadJsApiTicket()
        {
            return HttpHelper.Get(
                    $"https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token={GetAccessToken()}&type=jsapi")
                .Read().JsonTo<WXJsTicket>();
        }

        public WXJsSign GetJsApiSign(string decodeUrl)
        {
            var noncestr = Guid.NewGuid().ToString().Replace("-", "").ToLower();
            var timestamp = DateTime.Now.ToLongTime();
            var url = decodeUrl;
            if (url.Contains("#"))
            {
                url = url.Substring(0, url.IndexOf("#", StringComparison.Ordinal));
            }
            var map = new SortedDictionary<string, object>(StringComparer.Ordinal)
            {
                {"noncestr", noncestr},
                {"jsapi_ticket", GetJsApiTicket()},
                {"timestamp", timestamp},
                {"url", url}
            };
            var str1 = string.Join("&", map.Select(i => i.Key + "=" + i.Value));
            return new WXJsSign
            {
                appId = _appid,
                nonceStr = noncestr,
                timestamp = timestamp,
                signature = Encrypt.SHA1(str1).Base64ToHex()
            };
        }

        public WXUser GetUser(string openId)
        {
            return HttpHelper
                .Get($"https://api.weixin.qq.com/cgi-bin/user/info?access_token={GetAccessToken()}&openid={openId}")
                .Read().JsonTo<WXUser>();
        }

        public void SetMenu(WXButton[] buttons)
        {
            var body = new {button = buttons}.ToJson();
            var ret = HttpHelper
                .Post($"https://api.weixin.qq.com/cgi-bin/menu/create?access_token={GetAccessToken()}", body)
                .Read().JsonTo<WXReturn>();
            if (ret.errcode != 0)
            {
                throw new WXException(ret);
            }
        }

        public WXMessage ParseMessage(string xmlMsg)
        {
            var xml = new XmlDocument();
            xml.LoadXml(xmlMsg);
            var root = xml.DocumentElement;
            if (root == null)
                throw new FormatException(xmlMsg);

            var node = root.GetChildByName(nameof(WXMessage.MsgType));
            var msgType = node.Value.ToEnum<WXMsgType>($"{nameof(WXMessage.MsgType)}:{node.Value}。");

            switch (msgType.GetValueOrDefault())
            {
                case WXMsgType.Event:
                    node = root.GetChildByName(nameof(WXEventMsg.Event));
                    var msgEvent = node.Value.ToEnum<WXEventType>($"{nameof(WXEventMsg.Event)}:{node.Value}。");
                    switch (msgEvent.GetValueOrDefault())
                    {
                        case WXEventType.Click:
                            return new WXClickEvent(xmlMsg);
                        case WXEventType.View:
                            return new WXViewEvent(xmlMsg);
                        default:
                            throw new NotSupportedException(nameof(WXEventMsg.Event) + ":" +
                                                            msgEvent.GetValueOrDefault());
                    }
                case WXMsgType.Text:
                    return new WXTextMsg(xmlMsg);
                default:
                    throw new NotSupportedException(nameof(WXMessage.MsgType) + ":" + msgType.GetValueOrDefault());
            }
        }

        public WXJsChooseWXPay GetChooseWxPay(UnifiedorderResponse response, string pay_secret)
        {
            if (response.CheckSign(pay_secret) == false)
            {
                throw new WXException($"参数{nameof(pay_secret)}与统一下单使用的密钥不同。");
            }
            var map = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
            {
                {"appId", _appid},
                {"timeStamp", DateTime.Now.ToLongTime().ToString()},
                {"nonceStr", Guid.NewGuid().ToString().Replace("-", "").ToLower()},
                {"package", "prepay_id=" + response.prepay_id},
                {"signType", response.Request.sign_type.ToString()}
            };
            return new WXJsChooseWXPay
            {
                AppId = map[nameof(WXJsChooseWXPay.AppId)],
                TimeStamp = long.Parse(map[nameof(WXJsChooseWXPay.TimeStamp)]),
                NonceStr = map[nameof(WXJsChooseWXPay.NonceStr)],
                Package = map[nameof(WXJsChooseWXPay.Package)],
                SignType = map[nameof(WXJsChooseWXPay.SignType)],
                PaySign = WxSignHelper.PaySign(map, pay_secret, response.Request.sign_type)
            };
        }
    }
}
